/*
 * Copyright 2011 myTDev.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mytdev.application;

import com.mytdev.application.splash.SplashFrame;
import com.mytdev.application.service.ServiceException;
import com.mytdev.application.service.ApplicationServiceManager;
import com.mytdev.commons.action.AbstractAction;
import com.mytdev.commons.action.Action;
import com.mytdev.commons.action.ActionSequence;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The application singleton.
 * @author Yann D'Isanto
 */
public final class Application {

    private static final Logger LOG = LoggerFactory.getLogger(Application.class);
    private static final Application INSTANCE = new Application();
    private ApplicationEngine appEngine;
    private ApplicationServiceManager serviceManager;
    private JFrame mainView;
    private Initialization initialization = new Initialization();

    private Application() {
    }

    private Initialization getInitialization() {
        return initialization;
    }

    /**
     * @return the Application singleton.
     */
    public static Application getInstance() {
        return INSTANCE;
    }

    /**
     * @return the application main view.
     */
    public static JFrame getMainView() {
        return INSTANCE.mainView;
    }

    /**
     * @return the application name.
     */
    public static String getName() {
        return INSTANCE.appEngine.getApplicationMetaData().getName();
    }

    /**
     * @return the application version.
     */
    public static String getVersion() {
        return INSTANCE.appEngine.getApplicationMetaData().getVersion();
    }

    /**
     * @return the application description.
     */
    public static String getDescription() {
        return INSTANCE.appEngine.getApplicationMetaData().getDescription();
    }

    /**
     * @return the application vendor.
     */
    public static String getVendor() {
        return INSTANCE.appEngine.getApplicationMetaData().getVendor();
    }

    /**
     * @return the application website.
     */
    public static String getWebsite() {
        return INSTANCE.appEngine.getApplicationMetaData().getWebsite();
    }

    /**
     * Returns the property value according to the specified key, null if the 
     * property is not found.
     * @param key the property key.
     * @return the property value, null if the property is not found.
     */
    public static String getProperty(Enum<?> key) {
        return INSTANCE.appEngine.getProperty(key.toString());
    }

    /**
     * Returns the property value according to the specified key, null if the 
     * property is not found.
     * @param key the property key.
     * @return the property value, null if the property is not found.
     */
    public static String getProperty(String name) {
        return INSTANCE.appEngine.getProperty(name);
    }

    /**
     * Returns the service instance designated by the specified key.
     * @param service the service key.
     * @return the service instance.
     * @throws ServiceException if an error occurs when retreiving the service.
     */
    public static Object getService(Enum<?> service) throws ServiceException {
        return getService(service.toString());
    }

    /**
     * Returns the service instance, of the specified type, designated by the specified key.
     * @param service the service key.
     * @param serviceType the service type.
     * @return the service instance.
     * @throws ServiceException if an error occurs when retreiving the service.
     */
    public static <T> T getService(Enum<?> service, Class<T> serviceType) throws ServiceException {
        return getService(service.toString(), serviceType);
    }

    /**
     * Returns the service instance designated by the specified name.
     * @param service the service name.
     * @return the service instance.
     * @throws ServiceException if an error occurs when retreiving the service.
     */
    public static Object getService(String name) throws ServiceException {
        return INSTANCE.serviceManager.getService(name);
    }

    /**
     * Returns the service instance, of the specified type, designated by the specified name.
     * @param service the service name.
     * @param serviceType the service type.
     * @return the service instance.
     * @throws ServiceException if an error occurs when retreiving the service.
     */
    public static <T> T getService(String name, Class<T> serviceType) throws ServiceException {
        return INSTANCE.serviceManager.getService(name, serviceType);
    }

    /**
     * Starts the application.
     */
    public static void start() {
        if (SplashFrame.isAvailable()) {
            SplashFrame.launch(INSTANCE.getInitialization());
        } else {
            Initialization init = INSTANCE.getInitialization();
            init.addInitializationListener(new InitializationListener() {

                @Override
                public void appEngineLoaded() {
                    LOG.info("Application engine loaded");
                }

                @Override
                public void mainViewLoaded() {
                    LOG.info("Main view loaded");
                }

                @Override
                public void serviceLoaded(String service) {
                    LOG.info("Service " + service + " loaded");
                }

                @Override
                public void initializationCompleted() {
                    LOG.info("Initialization completed");
                }

                @Override
                public void errorOnLoadingAppEngine(Exception ex) {
                    LOG.error("Error on loading application engine", ex);
                }

                @Override
                public void loadingAppEngine() {
                    LOG.info("Loading application engine");
                }

                @Override
                public void errorOnLoadingMainView(Exception ex) {
                    LOG.error("Error on loading main view", ex);
                }

                @Override
                public void loadingMainView() {
                    LOG.info("Loading main view");
                }

                @Override
                public void errorOnLoadingService(String service, Exception ex) {
                    LOG.error("Error on loading service " + service, ex);
                }

                @Override
                public void loadingService(String service) {
                    LOG.info("Loading service " + service);
                }

                @Override
                public void launchingMainView() {
                    LOG.info("Launching main view");
                }
            });
            init.run();
        }
    }

    public static void addInitializationListener(InitializationListener listener) {
        INSTANCE.getInitialization().addInitializationListener(listener);
    }

    public static void removeInitializationListener(InitializationListener listener) {
        INSTANCE.getInitialization().removeInitializationListener(listener);
    }

    public class Initialization extends ActionSequence {

        private int serviceCount;
        private int loadedServiceCount = 0;
        private List<InitializationListener> listeners = new ArrayList<InitializationListener>();

        public Initialization() {
            super(new Action[]{
                        getAppEngineLoadingAction(),
                        getServicesLoadingAction(),
                        getMainViewLoadingAction(),
                        getMainViewLaunchingAction()
                    });
        }

        public void addInitializationListener(InitializationListener listener) {
            listeners.add(listener);
        }

        public void removeInitializationListener(InitializationListener listener) {
            listeners.remove(listener);
        }

        public int getLoadedServiceCount() {
            return loadedServiceCount;
        }

        public int getServiceCount() {
            return serviceCount;
        }

        private void fireAppEngineLoaded() {
            for (InitializationListener listener : listeners) {
                listener.appEngineLoaded();
            }
        }

        private void fireServiceLoaded(String service) {
            for (InitializationListener listener : listeners) {
                listener.serviceLoaded(service);
            }
        }

        private void fireMainViewLoaded() {
            for (InitializationListener listener : listeners) {
                listener.mainViewLoaded();
            }
        }

        private void fireErrorOnLoadingAppEngine(Exception ex) {
            for (InitializationListener listener : listeners) {
                listener.errorOnLoadingAppEngine(ex);
            }
        }

        private void fireErrorOnLoadingService(String service, Exception ex) {
            for (InitializationListener listener : listeners) {
                listener.errorOnLoadingService(service, ex);
            }
        }

        private void fireErrorOnLoadingMainView(Exception ex) {
            for (InitializationListener listener : listeners) {
                listener.errorOnLoadingMainView(ex);
            }
        }

        private void fireLaunchingMainView() {
            for (InitializationListener listener : listeners) {
                listener.launchingMainView();
            }
        }

        private void fireLoadingAppEngine() {
            for (InitializationListener listener : listeners) {
                listener.loadingAppEngine();
            }
        }

        private void fireLoadingService(String service) {
            for (InitializationListener listener : listeners) {
                listener.loadingService(service);
            }
        }

        private void fireLoadingMainView() {
            for (InitializationListener listener : listeners) {
                listener.loadingMainView();
            }
        }
    }

    private Action getAppEngineLoadingAction() {
        return new AbstractAction() {

            @Override
            public Result perform() {
                initialization.fireLoadingAppEngine();
                try {
                    ApplicationEngineBuilder appEngineBuilder = XMLApplicationEngineBuilder.getInstance();
                    appEngine = appEngineBuilder.build();
                    serviceManager = new ApplicationServiceManager(appEngine);
                    initialization.serviceCount = serviceManager.getAvailableServices().length;
                } catch (ApplicationException ex) {
                    initialization.fireErrorOnLoadingAppEngine(ex);
                    return Result.FAILURE;
                }
                initialization.fireAppEngineLoaded();
                sleep(200L);
                return Result.SUCCESS;
            }
        };
    }

    private Action getServicesLoadingAction() {
        return new AbstractAction() {

            @Override
            public Result perform() {
                for (String service : serviceManager.getAvailableServices()) {
                    if (serviceManager.isServiceToLoadOnStartUp(service)) {
                        initialization.fireLoadingService(service);
                        try {
                            serviceManager.loadService(service);
                        } catch (ServiceException ex) {
                            initialization.fireErrorOnLoadingService(service, ex);
                            return Result.FAILURE;
                        }
                        initialization.fireServiceLoaded(service);
                    }
                    initialization.loadedServiceCount++;
                    sleep(200L);
                }
                return Result.SUCCESS;
            }
        };
    }

    private Action getMainViewLoadingAction() {
        return new AbstractAction() {

            @Override
            public Result perform() {
                initialization.fireLoadingMainView();
                final Class<? extends JFrame> mainViewClass = appEngine.getMainView();
                if (mainViewClass != null) {
                    final Object obj = new Object();
                    SwingUtilities.invokeLater(new Runnable() {

                        @Override
                        public void run() {
                            try {
                                mainView = mainViewClass.newInstance();
                            } catch (Exception ex) {
                                initialization.fireErrorOnLoadingMainView(ex);
                                fireExceptionThrown(ex);
                            }
                            synchronized (obj) {
                                obj.notifyAll();
                            }
                        }
                    });

                    synchronized (obj) {
                        try {
                            obj.wait();
                            if (mainView == null) {
                                return Result.FAILURE;
                            }
                        } catch (InterruptedException ex) {
                            LOG.error(ex.getLocalizedMessage(), ex);
                            return Result.FAILURE;
                        }
                    }
                }
                initialization.fireMainViewLoaded();
                sleep(200L);
                return Result.SUCCESS;
            }
        };
    }

    private Action getMainViewLaunchingAction() {
        return new AbstractAction() {

            @Override
            public Result perform() {
                if (mainView != null) {
                    initialization.fireLaunchingMainView();
                    sleep(200L);
                    SwingUtilities.invokeLater(new Runnable() {

                        @Override
                        public void run() {
                            mainView.setVisible(true);
                        }
                    });
                }
                return Result.SUCCESS;
            }
        };
    }

    private static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException ex) {
            LOG.warn(ex.getLocalizedMessage(), ex);
        }
    }

    public static void laf(Class<? extends LookAndFeel> lafClass) {
        laf(lafClass.getName());
    }

    public static void laf(String lafClassName) {
        try {
            UIManager.setLookAndFeel(lafClassName);
        } catch (Exception ex) {
            LOG.error(null, ex);
        }
    }
}
